/*
 * processor.cpp
 *
 *  Created on: 20/08/2011
 *      Author: george
 */
#include <iostream>

#include <fstream>
#include <string>

#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>

#include <net/ethernet.h>
#include <arpa/inet.h>
#include <pcap.h>
#include <semaphore.h>
#include <iomanip>

#include "ethernetprocess.h"
#include "network.h"
#include "host.h"
#include "defines.h"
#include "session.h"
#include "types.h"
#include "ipprocess.h"
#include "processor.h"


#include "udpsessionmanager.h"
#include "icmperrorsessionmanager.h"
#include "node.h"

#include "info.h"

Network 	*Processor::net        = 0;
Adjacencia  *Processor::adjacencia = 0;


void Processor::processor(u_char *uservar, const struct pcap_pkthdr *pheader, const u_char *packet){

	//Esta classe guarda informações de execução do software
	Info * systemInfo = Info::getInstance();

//	A classe EthernetProcesso é capaz de processar um frame ethernet
	EthernetProcessor *ethernet;

	try{
		ethernet = new EthernetProcessor(pheader, packet);
	}catch(NoEthernetHeader e){

	}

//	Se o frame ethernet não transportar um datragrama IP não fazemos mais nada
	if ( !ethernet->isIp() )
	{
		systemInfo->nmDiscartedPackets++;
		return;
	}

	systemInfo->nmCapturedPackets++;

	Node *hostOrigem = new Node(ethernet->getIp()->ip_src);
	Node *hostDestino  = new Node(ethernet->getIp()->ip_dst);



//	A classe ipProcess é responsável por processar o cabelaho ip
	ipProcess *ipprocessor;


//	A classe ipprocessor consegue processar um pacote ip
	try{
		ipprocessor = new ipProcess(pheader, ethernet->getIp());
	}catch(NoTcpHeader e){
		cout << "No TCP Header" << endl;
		systemInfo->nmCapturedPackets--;
	}

	//	A classe NET representa nossa rede
	if ( !net){
		net = new Network();
	}

	unsigned posOrigem;
	unsigned posDestino;

	bool 	 origemInserido  = false;
	bool 	 destinoInserido = false;

	try {
		posOrigem = net->insert(hostOrigem);
		systemInfo->nmHosts++;
		origemInserido = true;

	}catch (DuplicatedNodeException e) {

	}

	try{
		posDestino = net->insert(hostDestino);
		systemInfo->nmHosts++;
		destinoInserido = true;
	}catch (DuplicatedNodeException e){
	}


	Session *session = 0;

	posOrigem = net->position(hostOrigem);
	posDestino = net->position(hostDestino);

//	1º Caso: Os dois hosts estavam fora da rede  nesta situação precisamos atualizar a lista de adjacencias de ambos
	if ( destinoInserido && origemInserido )
	{
//		Atualiza a lista de adjacencias do host de origem
		try{
			session = net->elements[posOrigem]->getAdjacencia()->insert(new Peer(ethernet->getIp()->ip_dst));
			systemInfo->nmSessionManagers++;
		}catch(DuplicatedPeerException e){

		}

//		Atualiza a lista de adjacencias do host de destino e faz com que os dois compartilhem um gerenciador de sessões
		try{
			net->elements[posDestino]->getAdjacencia()->insert(new Peer(ethernet->getIp()->ip_src, session));
		}catch(DuplicatedPeerException e){

		}

//	Os dois estavam na rede, precisamos saber se eles ja se comunicaram
	}else if ( !destinoInserido && !origemInserido ) {

		Peer *peerDestino = new Peer(ethernet->getIp()->ip_dst);
		try{
			bool existiaComunicacao = false;

//			Se ocorrer uma exceção aqui existia comunicação entre os dois hosts
			try{
				session = net->elements[posOrigem]->getAdjacencia()->insert(peerDestino);
				systemInfo->nmSessionManagers++;
				existiaComunicacao = true;

			// Se a exceção ocorrer vamos pegar a sessao que ja existia entre os dois
			}catch(DuplicatedPeerException e){
				session = net->elements[posOrigem]->getAdjacencia()->root->peer->session;
			}

			// Se ja existia comunicação não precisamos testar o outro lado
			if ( !existiaComunicacao )
			{
				Peer *peerOrigem = new Peer(ethernet->getIp()->ip_dst, session);

				try{
					net->elements[posDestino]->getAdjacencia()->insert(peerOrigem);
				}catch(DuplicatedPeerException e){

				}
			}

		}catch(DuplicatedPeerException e){
//			Se a execução chegar a este ponto, isto significa que existia uma comunicação prévia entre os dois hosts
			session = net->elements[posOrigem]->getAdjacencia()->root->peer->session;
		}

	// Se apenas um foi inserido precisamos atualiza as listas de adjacencias
	}else{

//		Atualiza a lista de adjacencias do host de origem
		try{
			session = net->elements[posOrigem]->getAdjacencia()->insert(new Peer(ethernet->getIp()->ip_dst));
			systemInfo->nmSessionManagers++;
		}catch(DuplicatedPeerException e){

		}

//		Atualiza a lista de adjacencias do host de destino e faz com que os dois compartilhem um gerenciador de sessões
		try{
			net->elements[posDestino]->getAdjacencia()->insert(new Peer(ethernet->getIp()->ip_src, session));
		}catch(DuplicatedPeerException e){

		}
	}

	if ( !origemInserido  )
	{
		delete hostOrigem;
	}

	if ( !destinoInserido )
	{
		delete hostDestino;
	}

	if ( ipprocessor->isICMP() ){
		if ( ipprocessor->isICMPInformation() ){
			session->addIcmp(&ethernet->getIp()->ip_src, &ethernet->getIp()->ip_dst, ipprocessor->getICMP());
		}else{
			session->addIcmpError(&ethernet->getIp()->ip_src, &ethernet->getIp()->ip_dst, ipprocessor->getICMP());
		}
	}else if ( ipprocessor->isUDP()){

		session->addUdp(ethernet->getIp(), ipprocessor->getUdp());

	}else if ( ipprocessor->isTCP()){
		session->addTcp(ethernet->getIp(), ipprocessor->getTcp());
	}else{
		systemInfo->nmCapturedPackets--;
		systemInfo->nmDiscartedPackets++;
	}

}

